---
description: A guide on migrating to Plasmo from any browser extension setup.
---

# Migrate to Plasmo Framework

The Plasmo Browser Extension Framework simplifies building browser extensions with its declarative approach. It eliminates boilerplate code and provides a file-based configuration system that is easy to understand, use and [opt out of](#opting-out).

In this guide, we will walk you through transitioning from any extension project to Plasmo and highlight some key changes you need to make.

## Install Plasmo CLI

The Plasmo framework's main driver is the Plasmo CLI. It is a Node.js package that contains a compiler, a bundler, a development server, and a packager tailor-made for browser extensions:

```bash
pnpm install plasmo
```

To start the development server, run `plasmo dev`. To build the extension, run `plasmo build`. To package the extension, run `plasmo package`.

## manifest.json

Plasmo merges `manifest.json` into the `package.json` and abstracts away the most basic properties:

| Manifest Field              | Abstractions                                                           |
| :-------------------------- | :--------------------------------------------------------------------- |
| `icons`                     | Auto generated with the `icon.png` in the `assets` directory           |
| `action`, `browser_actions` | [`popup.tsx`](#popup-options-newtab-pages)                             |
| `options_ui`                | [`options.tsx`](#popup-options-newtab-pages)                           |
| `content_scripts`           | [`contents/*.{ts,tsx}`, `content.ts`, `content.tsx`](#content-scripts) |
| `background`                | [`background.ts`](#background-service-worker)                          |
| `sandbox`                   | `sandbox.tsx`, [Sandbox Pages](/framework/sandbox-pages)               |
| `manifest_version`          | set by the `--target` build flag, default to `3`                       |
| `version`                   | set by the `version` field in `package.json`                           |
| `name`                      | set by the `displayName` field in `package.json`                       |
| `description`               | set by the `description` field in `package.json`                       |
| `author`                    | set by the `author` field in `package.json`                            |
| `homepage_url`              | set by the `homepage` field in `package.json`                          |

Plasmo centralizes common metadata between `package.json` and `manifest.json` and resolves any static file references (such as action, background, content scripts, and so on) automatically.

This enables you to focus on the metadata that matters, such as name, description, OAuth, declarative_net_request, and so on.

Furthermore, it enables the framework to provide even more powerful features, such as:

- [Using environment variables in the manifest](/framework/env#in-manifest-overrides)
- [`node_modules` resolving for web-accessible resources](/framework/assets#assets-from-node_modules)
- [Targeting multiple browsers, manifest versions, and environments (dev, prod, staging, etc...)](/framework/workflows/build#with-a-specific-target)

## Popup, Options, New tab Pages

Plasmo removes the need to manually mount your React/Svelte/Vue components.

In a non-Plasmo extension project, to mount a React component, you'd create an `index.html` template and associated JavaScript code:

```html filename="popup.html"
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Popup</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="popup.js"></script>
  </body>
</html>
```

```js filename="popup.jsx"
import { createRoot } from "react"

import Popup from "./core/popup"

const root = document.getElementById("root")
createRoot(root).render(<Popup />)
```

If you wanted to add TypeScript, LESS, or SCSS, you would need to use Webpack, Parcel, or ESBuild, with extra plugins and loader setups.

With the Plasmo framework, it's much simpler. Add a `popup.tsx` or `options.tsx` file in [the source code directory](/framework/customization/src), exporting a default React component.

Plasmo will take care of the rest:

```tsx filename="popup.tsx"
import Popup from "./core/popup"

export default Popup
```

Plasmo has built-in support for CSS, SCSS, LESS, CSS Modules, and PostCSS plugins.

If you'd like to, for example, use SCSS, you can simply import the SCSS file in your React component:

```tsx filename="popup.tsx"
import Popup from "./core/popup"

import "./popup.scss"

export default Popup
```

You can mount a React component this way for the options page, new tab page, [sandbox pages](/framework/sandbox-pages), and any other [custom page](#custom-pages).

## Custom Pages

In non-Plasmo extension projects, creating custom pages typically requires additional HTML templates and associated JavaScript code:

```html filename="tabs/custom.html"
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Custom Page</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="./custom.js"></script>
  </body>
</html>
```

```js filename="tabs/custom.js"
import { createRoot } from "react"

import CustomPage from "./core/custom-page"

const root = document.getElementById("root")
createRoot(CustomPage).render(<PopupApp />)
```

With Plasmo, you can use the built-in tab pages feature to create custom pages easily. Add a **`tabs`** folder in [you source code directory](/framework/customization/src), and add your custom pages there, using `.tsx` files:

```ts filename="tabs/custom.tsx"
import Hello from "./core/hello"

export default Hello
```

The page will be available at `chrome-extension://<extension-id>/tabs/custom.html`.

## Content Scripts

In non-Plasmo extension projects, you'd specify your content scripts in the `manifest.json` file and write them in JavaScript:

```json filename="manifest.json"
{
  "content_scripts": [
    {
      "matches": ["https://*/*", "http://*/*"],
      "all_frames": true,
      "css": ["content.css"],
      "run_at": "document_start"
    }
  ]
}
```

```js filename="content.js"
console.log("Hello from content script!")
```

With Plasmo, your can specify your content script and its respective config in a file called `content.ts`, or inside a directory called `contents` - More on that later!

Here's what the `content.ts` file would look like:

```ts filename="content.ts"
import type { PlasmoCSConfig } from "plasmo"

export const config: PlasmoCSConfig = {
  matches: ["https://*/*", "http://*/*"],
  all_frames: true,
  css: ["~content.css"]
}

console.log("Hello from content script!")
```

No more moving back and forth between your `manifest.json` and `content.js` files. An added benefit is that the config is fully typed and thus can provide valuable auto-completion from your IDE.

To add multiple content scripts, create a directory called `contents` and repeat the above steps. You can name your scripts whatever you'd like in this directory. They will all get added as content scripts with their own configs.

For example, let's say you want to add a content script that changes the background color of the page to green. You can do so by creating a file called `emerald-splash.ts` in the `contents` directory:

```ts filename="contents/emerald-splash.ts"
document.body.style.backgroundColor = "green"
```

Since the content script file didn't export a config, Plasmo will use the default config:

```json
{
  "matches": ["<all_urls>"]
}
```

## Content Scripts UI

If you're injecting a React component into a web page, your extension likely has a lot of boilerplate code, creating Shadow DOMs, finding the right element to mount to, MutationObservers, and more.

We've abstracted all of this out so you can focus on building your component and not worry about the rest.

For example, let's say you want to inject a React component of a simple button into a web page. You can do so by creating a file called `press-me.tsx` in the `contents` directory:

```tsx filename="contents/press-me.tsx"
export default function PressMeCSUI() {
  return <button>Press me</button>
}
```

Plasmo will automatically inject this component into the page, and mount it to the root element of the page.

What if you want to inject the component into a specific element? You can do so by exporting a getInlineAnchor function from the file:

```tsx filename="contents/press-me.tsx"
import type { PlasmoGetInlineAnchor } from "plasmo"

export const getInlineAnchor: PlasmoGetInlineAnchor = async () =>
  document.querySelector("#pricing")
```

By returning an Element, Plasmo will mount the component adjacent to that element. You can completely customize the mounting behavior, how the component renders, and more.

This feature is called [Content Scripts UI](/framework/content-scripts-ui). To learn more about its API and how it works, check out the technical documentation on [its lifecycle](/framework/content-scripts-ui/life-cycle).

## Background Service Worker

In non-Plasmo projects, creating a background service worker requires specifying the `background` property in the `manifest.json` file and writing the service worker code in JavaScript:

```json filename="manifest.json"
{
  "background": {
    "service_worker": "background.js"
  }
}
```

```js filename="background.js"
console.log("Hello from BGSW!")
```

In Plasmo, you specify the background service worker by creating a `background.ts` file:

```ts filename="background.ts"
console.log("Hello from BGSW!")
```

You can import any modules that target the standard service worker runtime into `background.ts`. For example, you can add the `bip39` module:

```ts filename="background.ts"
import { generateMnemonic } from "bip39"

console.log(
  "Live now; make now always the most precious time. Now will never come again."
)
console.log(generateMnemonic())
```

Plasmo will automatically bundle the `background.ts` file and add it as a background service worker to the `manifest.json` file.

## Environment Variables

If you're currently using environment variables, you can continue to do so in Plasmo.

The Plasmo framework supports [environment variables out of the box](/framework/env) with no additional setup required.

## Opting Out

The Plasmo framework's abstraction philosophy is to remove the most common configuration and boilerplate code. This enables developers to work under a higher abstraction layer -- their chosen UI library/framework, such as React, Vue, and Svelte. This is the path to creating more powerful and beautiful extensions.

Opting out of Plasmo is as easy as removing the `plasmo` dependency from your `package.json` file and taking your components out onto your custom setups. This is possible because all the glue code generated by Plasmo is injected at the framework compiler and bundler layer, making your feature code extremely portable.

You can also use Plasmo as much or as little as needed. All chrome APIs are available, and you can use them directly in your feature code. For example, instead of using the [messaging API](/framework/messaging), you can use the `chrome.runtime.messaging` API directly. The same goes for content scripts and extension pages.
